home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / WAIS / next-ui / WaisDocument.m < prev    next >
Encoding:
Text File  |  1992-06-08  |  17.9 KB  |  609 lines

  1. // WaisDocument.m
  2. //
  3. // Free software created 1 Feb 1992
  4. // by Paul Burchard <burchard@math.utah.edu>.
  5. // Incorporating:
  6. /* 
  7.    WIDE AREA INFORMATION SERVER SOFTWARE:
  8.    No guarantees or restrictions.  See the readme file for the full standard
  9.    disclaimer.
  10.  
  11.    This is part of the [NeXTstep] user-interface for the WAIS software.
  12.    Do with it as you please.
  13.  
  14.    Version 0.82
  15.    Wed Apr 24 1991
  16.  
  17.    jonathan@Think.COM
  18.  
  19. */
  20. //
  21.  
  22. #import "WaisDocument.h"
  23.  
  24. // Search path for documents.
  25. static id documentFolderList;
  26.  
  27. // Error panel title.
  28. static char *errorTitle = "WAIS Document Error!";
  29.  
  30. // Decoders for WAIS structured files.
  31.  
  32. _WaisDecoder waisSourceIDDecoder[] = 
  33. {
  34.     { ":filename",        W_FIELD,0,0,    ReadString,3,    WriteString,2,
  35.                             MAX_SYMBOL_SIZE },
  36.     { NULL }
  37. };
  38.  
  39. _WaisDecoder waisDocumentDecoder[] = 
  40. {
  41.     { ":number-of-lines",    W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  42.     { ":number-of-bytes",    W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  43.     { ":number-of-characters",    W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  44.     { ":best-line",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  45.     { ":date",            W_FIELD,0,0,    ReadString,3,    WriteString,2,
  46.                             MAX_SYMBOL_SIZE },
  47.     { ":headline",        W_FIELD,0,0,    ReadString,3,    WriteString,2,
  48.                             MAX_SYMBOL_SIZE },
  49.     { ":type",            W_FIELD,0,0,    ReadString,3,    WriteString,2,
  50.                             MAX_SYMBOL_SIZE },
  51.     { ":source",        W_STRUCT,
  52.     ":source-id",        waisSourceIDDecoder },
  53.     { ":doc-id",        W_STRUCT,
  54.         ":doc-id",        NULL/*special case*/ },
  55.     { NULL }
  56. };
  57.  
  58. _WaisDecoder waisFragmentDecoder[] = 
  59. {
  60.     { ":para-id",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  61.     { ":line-pos",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  62.     { ":byte-pos",        W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  63.     { NULL }
  64. };
  65.  
  66. _WaisDecoder waisDocumentIDDecoder[] = 
  67. {
  68.     { ":score",            W_FIELD,0,0,    ReadLongS,2,    WriteLongS,2 },
  69.     { ":document",        W_STRUCT,
  70.     ":document",        waisDocumentDecoder },
  71.     { ":start",            W_STRUCT,
  72.     ":fragment",        waisFragmentDecoder },
  73.     { ":end",            W_STRUCT,
  74.     ":fragment",        waisFragmentDecoder },
  75.     { NULL }
  76. };
  77.  
  78.  
  79. @implementation WaisDocument
  80.  
  81. + folderList
  82. {
  83.     return documentFolderList;
  84. }
  85.  
  86. + setFolderList:aList
  87. {
  88.     if(documentFolderList) [documentFolderList free];
  89.     documentFolderList = aList;
  90.     return self;
  91. }
  92.  
  93. + (const char *)defaultHomeFolder
  94. {
  95.     return "/Library/WAIS/documents";
  96. }
  97.  
  98. + (const char *)fileStructName
  99. {
  100.     return ":document-id";
  101. }
  102.  
  103. + (WaisDecoder)fileStructDecoder
  104. {
  105.     return waisDocumentIDDecoder;
  106. }
  107.  
  108. + (const char *)errorTitle
  109. {
  110.     return errorTitle;
  111. }
  112.  
  113. + (BOOL)checkFileName:(const char *)fileName
  114. {
  115.     // We read in the .wais file corresponding to the doc's content file.
  116.     if(!fileName) return NO;
  117.     if(strlen(fileName) <= strlen(W_D_EXT)) return NO;
  118.     if(!strstr(fileName, W_D_EXT)) return NO;
  119.     if(0 != strcmp(W_D_EXT, strstr(fileName, W_D_EXT))) return NO;
  120.     return YES;
  121. }
  122.  
  123. - free
  124. {
  125.     if(waisDocID) s_free(waisDocID);
  126.     return [super free];
  127. }
  128.  
  129. + objectForCompleteKey:(const char *)aKey
  130. {
  131.     char *buf, *endp;
  132.     id found;
  133.  
  134.     // First remove any ".wais" extension from keys, then call standard method.
  135.     if(!aKey || !strstr(aKey, W_D_EXT))
  136.         return [super objectForCompleteKey:aKey];
  137.     if(!(buf = s_malloc(strlen(aKey)+1))) return nil;
  138.     strcpy(buf, aKey);
  139.     if(!(endp = strstr(buf, W_D_EXT))) { s_free(buf); return nil; }
  140.     *endp = 0;
  141.     found = [super objectForCompleteKey:buf];
  142.     s_free(buf);
  143.     if(found) return found;
  144.     else return nil;
  145. }
  146.  
  147. - setKey:(const char *)aKey
  148. {
  149.     char *buf, *endp;
  150.     id rtn;
  151.     
  152.     // First remove any ".wais" extension from keys.
  153.     if(!aKey || !(endp = strstr(aKey, W_D_EXT)))
  154.         return [super setKey:aKey];
  155.     if(!(buf = s_malloc(strlen(aKey)+1))) return nil;
  156.     strcpy(buf, aKey);
  157.     if(!(endp = strstr(buf, W_D_EXT))) return nil;
  158.     *endp = 0;
  159.     rtn = [super setKey:buf];
  160.     s_free(buf);
  161.     return rtn;
  162. }
  163.  
  164. - setKeyFromInfo
  165. {
  166.     char *buf, *p, *hname, *extn, *jump;
  167.     const char *head, *src, *src_end, *src_ext;
  168.     const char *headline, *theType;
  169.     const char **foldp, *fold;
  170.     int len, extcnt, non_blank;
  171.     
  172.     // HACKING WAIS HEADLINES INTO FILE NAMES AND TYPES.
  173.     //
  174.     //!!! This is a hack.  WAIS servers should make better use of TYPE field.
  175.     //
  176.     // Uses the ":headline", ":type", and [source] ":filename" info fields,
  177.     // information in the fromSource member.
  178.     //
  179.     // The "key" member is set to the headline altered with this recipe:
  180.     //     1. Everything between the first and last '/' inclusive, is removed
  181.     //         and replaced by a space.  If the first '/' was directly 
  182.     //         preceeded by a blank, but the initial portion is not totally 
  183.     //         blank, all following text is removed as well.
  184.     //     2. Trailing & leading blanks of the new string are removed,
  185.     //         sequences of blanks are compressed into single space chars,
  186.     //         and non-ascii chars are replaced by '?'.
  187.     //     3. IF the string is now empty it is replaced by "?".
  188.     //     4. ELSE IF the new string ends with a file extension
  189.     //         ("[.][A-Za-z0-9]+"), then the extension is lowercased.
  190.     //     5. If the waisType input is non-NULL and different from "TEXT",
  191.     //           then the extension is changed to reflect the waisType.
  192.     //     6. As long as the document is not a WAIS source itself (according to
  193.     //         ":type" field), the source's ":filename", excluding the ".src" 
  194.     //         extension but followed by a ':', is prepended.  Or, if the 
  195.     //         ":filename" info field is blank, our ":filename" field is
  196.     //         tried (this would come from a source), or lastly the 
  197.     //         final component of the source's "key" member is used instead.
  198.     //     7. The default folder for WaisDocuments is prepended (with 
  199.     //         separating '/' if necessary), unless the document is a WAIS 
  200.     //         source, in which case the default folder for WaisSources is 
  201.     //         prepended instead.
  202.     //
  203.     headline = [self valueForStringKey:":headline"];
  204.     theType = [self valueForStringKey:":type"];
  205.     if(theType && 0==strcmp(theType, "WSRC"))
  206.         foldp = (const char **)[[WaisSource folderList] elementAt:0];
  207.     else foldp = (const char **)[[WaisDocument folderList] elementAt:0];
  208.     if(foldp) fold = *foldp;
  209.     else fold = "/";
  210.     if(!(fromSource && (src=[fromSource valueForStringKey:":filename"]))
  211.     && !(src = [self valueForStringKey:":filename"])
  212.         && !(fromSource && (src=[fromSource key])))
  213.     src = "?";
  214.     if(strrchr(src, '/')) { src = strrchr(src, '/'); src++; }
  215.     if(headline) len = strlen(headline);
  216.     else len = strlen("?");
  217.     if(!(buf = s_malloc(strlen(fold) + strlen(src) + len + strlen("/:.tiff"))))
  218.         return nil;
  219.     strcpy(buf, fold);
  220.     p = buf + strlen(buf);
  221.     if(buf[0] != '/') return nil;
  222.     if(*(p-1) != '/') *p++ = '/';
  223.     src_end = src + strlen(src);
  224.     if(strlen(src)<4 || 0!=strcmp((src_ext=src_end-4), ".src"))
  225.         src_ext = src_end;
  226.     if(!theType || 0!=strcmp(theType, "WSRC"))
  227.        { for(; src<src_ext; src++) *p++ = *src; *p++ = ':'; }
  228.     *(hname=p) = 0;
  229.     if(headline) head = headline;
  230.     else head = "?";
  231.     for(; isascii(*head) && isspace(*head); head++);
  232.     for(non_blank=0; *head; head++)
  233.         if(!(isascii(*head) && isspace(*head)
  234.         && isascii(*(p-1)) && isspace(*(p-1))))
  235.     {
  236.         if(*head == '/')
  237.         {
  238.             if(non_blank && isascii(*(p-1)) && isspace(*(p-1))) break;
  239.         else
  240.         {
  241.             if(!(jump = strrchr(head, '/'))) jump = head;
  242.             head = jump;
  243.             *p++ = ' ';
  244.             continue;
  245.         }
  246.         }
  247.         if(isascii(*head) && isspace(*head)) *p++ = ' ';
  248.         else { *p++ = *head; non_blank = 1; }
  249.     }
  250.     for(p--; p>=hname && isascii(*p) && isspace(*p); p--);
  251.     *++p = 0;
  252.     for(p=hname; *p; p++) if(!isascii(*p)) *p = '?';
  253.     extn = 0;
  254.     if(p == hname) { *p++ = '?'; *p = 0; }
  255.     else
  256.     {
  257.         p = hname + strlen(hname);
  258.     for(extcnt=0, p--; p>hname && isalnum(*p); p--) extcnt++;
  259.     if(*p=='.' && extcnt>0)
  260.         { for(extn=p, p++; *p; p++) if(isupper(*p)) *p = tolower(*p); }
  261.     }
  262.     if(theType && 0!=strcmp(theType, "TEXT"))
  263.     {
  264.     if(0 == strcmp(theType, "WSRC"))
  265.         { if(extn) strcpy(extn, ".src"); else strcat(buf, ".src"); }
  266.     else if(0 == strcmp(theType, "TIFF"))
  267.         { if(extn) strcpy(extn, ".tiff"); else strcat(buf, ".tiff"); }
  268.     else if(0 == strcmp(theType, "GIF"))
  269.         { if(extn) strcpy(extn, ".gif"); else strcat(buf, ".gif"); }
  270.     }
  271.     [self setKey:buf];
  272.     s_free(buf);
  273.     return self;
  274. }
  275.  
  276. - fromSource
  277. {
  278.     return fromSource;
  279. }
  280.  
  281. - setFromSource:aSource
  282. {
  283.     const char *src;
  284.     
  285.     fromSource = aSource;
  286.     isRetrieved = NO;
  287.     if(fromSource && [fromSource valueForStringKey:":filename"])
  288.     [self insertStringKey:":filename"
  289.         value:[fromSource valueForStringKey:":filename"]];
  290.     else if(fromSource && [fromSource key])
  291.     {
  292.         src = [fromSource key];
  293.     if(strrchr(src, '/')) { src = strrchr(src, '/'); src++; }
  294.     [self insertStringKey:":filename" value:src];
  295.     }
  296.     return self;
  297. }
  298.  
  299. - (DocID *)waisDocID
  300. {
  301.     return waisDocID;
  302. }
  303.  
  304. // theDocID must be s_free()-able.
  305. - setWaisDocID:(DocID *)theDocID
  306. {
  307.     if(waisDocID) s_free(waisDocID);
  308.     waisDocID = theDocID;
  309.     return self;
  310. }
  311.  
  312. - setWaisDocIDFromAny:(any *)docAny
  313. {
  314.     isRetrieved = NO;
  315.     if(waisDocID) s_free(waisDocID);
  316.     if(!(waisDocID = docIDFromAny(docAny)))
  317.     {
  318.     waisDocID = (DocID *)s_malloc(sizeof(DocID));
  319.     waisDocID->originalLocalID = copy_any(docAny);
  320.     }
  321.     return self;
  322. }
  323.  
  324. - (BOOL)isRetrieved
  325. {
  326.     return isRetrieved;
  327. }
  328.  
  329. - setUnretrieved
  330. {
  331.     isRetrieved = NO;
  332.     return self;
  333. }
  334.  
  335. - cleanUpClose:(FILE *)file free:(any *)ptr
  336. {
  337.     [Wais lockFileIO]; fclose(file); [Wais unlockFileIO];
  338.     if(ptr) s_free(ptr);
  339.     return nil;
  340. }
  341.  
  342. - retrieve
  343. {
  344.     int i;
  345.     long lines, size, count, chars, length;
  346.     long request_length, chars_per_page;
  347.     const char *value, *database, *wType;
  348.     static char request[MAX_MESSAGE_LEN], response[MAX_MESSAGE_LEN];
  349.     FILE *file;
  350.     any* docany;
  351.     WAISDocumentText *data;
  352.     SearchResponseAPDU *interp_response;
  353.     diagnosticRecord **diag;
  354.     extern char *delete_seeker_codes();/* in ui.c, but not declared in ui.h */
  355.  
  356.     // Set up source for retrieval.
  357.     isRetrieved = NO;
  358.     [fromSource setConnected:YES];
  359.     if(![fromSource isConnected]) return nil;
  360.  
  361.     // Open local document file to receive retrieved data.
  362.     [Wais lockFileIO];
  363.     if(!key || !(file = fopen(key, "w")))
  364.     {
  365.     [Wais unlockFileIO];
  366.     ErrorMsg(errorTitle, "Can't create local document file %s.",
  367.         key ? key : "???");
  368.     return nil;
  369.     }
  370.     [Wais unlockFileIO];
  371.  
  372.     // parameters for "page-by-page" retrieval loop.
  373.     if(value=[self valueForStringKey:":number-of-lines"]) lines = atol(value);
  374.     else lines = 0;
  375.     if(value=[self valueForStringKey:":number-of-bytes"]) chars = atol(value);
  376.     else if(value=[self valueForStringKey:":number-of-characters"])
  377.     chars = atol(value);
  378.     else chars = 0;
  379.     size = 0;
  380.     chars_per_page = [fromSource bufferLength]-HEADER_LENGTH-1000;/*paranoia?*/    
  381.     docany = anyFromDocID(waisDocID);
  382.     database = [fromSource valueForStringKey:":database-name"];
  383.     wType = [self valueForStringKey:":type"];
  384.     if(!wType) wType = "TEXT";
  385.     if(lines<=0 && chars<=0)
  386.     {
  387.     [self cleanUpClose:file free:docany];
  388.         ErrorMsg(errorTitle, "Document %s is empty.", key);
  389.     return nil;
  390.     }
  391.     
  392.     // Retrieve one page at a time and write to local doc file.
  393.     for(count=0; count*chars_per_page<chars; count++)
  394.     {
  395.         // Lock transaction to prevent conflict with port.
  396.     [Wais lockTransaction];
  397.     
  398.     // Create retrieval request message.
  399.     request_length = [fromSource bufferLength];
  400.     if(!generate_retrieval_apdu(request + HEADER_LENGTH,
  401.         &request_length, docany, CT_byte, count * chars_per_page,
  402.         MIN((count + 1) * chars_per_page, chars), wType, database))
  403.     {
  404.         [Wais unlockTransaction]; [self cleanUpClose:file free:docany];
  405.         ErrorMsg(errorTitle, "Overflow: retrieval request too large for %s.", key);
  406.         return nil;
  407.     }
  408.     
  409.     // Send retrieval message.
  410.     if(!interpret_message(request, MAX_MESSAGE_LEN - request_length,
  411.         response, MAX_MESSAGE_LEN, [fromSource connection], false))
  412.     {
  413.         [Wais unlockTransaction]; [self cleanUpClose:file free:docany];
  414.         ErrorMsg(errorTitle,"Warning: missing data for document %s.",key);
  415.         return nil;
  416.     }
  417.  
  418.     // Interpret received reply message.
  419.     // Transaction is done; unlock.
  420.     readSearchResponseAPDU(&interp_response, response + HEADER_LENGTH);
  421.     [Wais unlockTransaction];
  422.     if(interp_response
  423.         && (WAISSearchResponse *)interp_response
  424.             ->DatabaseDiagnosticRecords 
  425.         && (diag = ((WAISSearchResponse *)interp_response
  426.             ->DatabaseDiagnosticRecords)->Diagnostics)
  427.         )
  428.         for(i=0; diag[i]; i++) if(diag[i]->ADDINFO)
  429.             ErrorMsg(errorTitle, "Retrieval diagnostics: %s, %s",
  430.             diag[i]->DIAG, diag[i]->ADDINFO);
  431.     
  432.     // Extract document data chunk from response.
  433.     // If of file type "TEXT", strip out weird stuff.
  434.     // (Note "TEXT" type is ASCII-based, not international.)
  435.     if(!((WAISSearchResponse *)interp_response
  436.         ->DatabaseDiagnosticRecords)->Text)
  437.     {
  438.         [self cleanUpClose:file free:docany];
  439.         ErrorMsg(errorTitle,"Warning: missing data for document %s.",key);
  440.         return nil;
  441.     }
  442.     data = ((WAISSearchResponse *)interp_response
  443.         ->DatabaseDiagnosticRecords)->Text[0];
  444.     if(0 == strcmp(wType, "TEXT"))
  445.     {
  446.         length = data->DocumentText->size;
  447.         delete_seeker_codes(data->DocumentText->bytes, &length);
  448.         data->DocumentText->size = length;
  449.         replace_controlM(data->DocumentText->bytes, &length);
  450.         data->DocumentText->size = length;
  451.     }
  452.     size += data->DocumentText->size;
  453.  
  454.     // Write data chunk to file.
  455.     [Wais lockFileIO];
  456.     if(data->DocumentText->size
  457.         != fwrite(data->DocumentText->bytes, sizeof(char), 
  458.         (size_t)data->DocumentText->size, file))
  459.     {
  460.         [Wais unlockFileIO]; [self cleanUpClose:file free:docany];
  461.         ErrorMsg(errorTitle, "Write error on document %s.", key);
  462.         return nil;
  463.     }
  464.     [Wais unlockFileIO];
  465.     }
  466.     [self cleanUpClose:file free:docany];
  467.     [Wais lockTransaction];
  468.     isRetrieved = YES;
  469.     [Wais unlockTransaction];
  470.     return self;
  471. }
  472.  
  473. - (short)readWaisStruct:(const char *)structName
  474.     forElement:(const char *)elementName
  475.     fromFile:(FILE *)file
  476.     withDecoder:(WaisDecoder)theDecoder
  477. {
  478.     short check_result;
  479.     DocID *docid;
  480.     
  481.     // Use doc-id shortcut routine.
  482.     if(0 == strcmp(structName, ":doc-id"))
  483.     {
  484.     if(!(docid = (DocID *)s_malloc(sizeof(DocID)))) return FALSE;
  485.     check_result = ReadDocID(docid, file);
  486.     if(check_result==FALSE || check_result==END_OF_STRUCT_OR_LIST)
  487.         { s_free(docid); return check_result; }
  488.     [self setWaisDocID:docid];
  489.     return check_result;
  490.     }
  491.     
  492.     // Standard read.
  493.     check_result = [super readWaisStruct:structName
  494.     forElement:elementName fromFile:file withDecoder:theDecoder];
  495.  
  496.     // We flatten the WAIS document file structure for convenience,
  497.     // so must avoid confusing ":start", ":end" subfields (both are frags).
  498.     if(0==strcmp(elementName, ":start") || 0==strcmp(elementName, ":end"))
  499.     {
  500.     if([self valueForStringKey:":byte-pos"])
  501.             [self insertStringKey:elementName
  502.             value:[self valueForStringKey:":byte-pos"]];
  503.     else if([self valueForStringKey:":line-pos"])
  504.             [self insertStringKey:elementName
  505.             value:[self valueForStringKey:":line-pos"]];
  506.     else if([self valueForStringKey:":para-id"])
  507.         [self insertStringKey:elementName
  508.             value:[self valueForStringKey:":para-id"]];
  509.     }
  510.     
  511.     // Find source if necessary.
  512.     if(0==strcmp(structName, ":source-id"))
  513.     {
  514.         [self setFromSource:[WaisSource objectForKey:[self 
  515.         valueForStringKey:":filename"]]];
  516.     if(!fromSource) ErrorMsg(errorTitle, "Unknown source %s.",
  517.         [self valueForStringKey:":filename"]);
  518.     }
  519.     
  520.     // Set key from info, if still NULL even tho full doc record has been read.
  521.     if(!key && 0==strcmp(structName, [WaisDocument fileStructName]))
  522.         { isRetrieved = NO; [self setKeyFromInfo]; }
  523.     return check_result;
  524. }
  525.  
  526. - readWaisFile
  527. {
  528.     NXAtom orig_key;
  529.     char buf[MAXPATHLEN+1];
  530.     
  531.     // We read WAIS specification file rather than content file,
  532.     //     so temporarily append ".wais" to key (note call to super
  533.     //     since our -setKey: strips the ".wais").
  534.     if(!key) return nil;
  535.     orig_key = key;
  536.     strcpy(buf, orig_key);
  537.     strcat(buf, W_D_EXT);
  538.     [super setKey:buf];
  539.     if(![super readWaisFile]) return nil;
  540.     [self setKey:orig_key];
  541.     
  542.     // Mark doc as retrieved if file named by (original) key exists.
  543.     [Wais lockFileIO];
  544.     if(0 == access(key, R_OK)) isRetrieved = YES;
  545.     else isRetrieved = NO;
  546.     [Wais unlockFileIO];
  547.     return self;
  548. }
  549.  
  550. - (short)writeWaisStruct:(const char *)structName
  551.     forElement:(const char *)elementName
  552.     toFile:(FILE *)file
  553.     withDecoder:(WaisDecoder)theDecoder
  554. {
  555.     // Use doc-id shortcut routine.
  556.     if(0 == strcmp(structName, ":doc-id"))
  557.     {
  558.         if(waisDocID)
  559.         { WriteDocID(waisDocID, file); WriteNewline(file); return TRUE; }
  560.     else { ErrorMsg(errorTitle, "No Doc-ID for %s.", key); return FALSE; }
  561.     }    
  562.     
  563.     // We flatten the WAIS document file structure for convenience,
  564.     // so must avoid confusing ":start", ":end" subfields (both are frags).
  565.     if(0==strcmp(elementName, ":start") || 0==strcmp(elementName, ":end"))
  566.     {
  567.         //!!! note kludge from xwais: we ignore distinctions here!
  568.         [self insertStringKey:":byte-pos"
  569.         value:[self valueForStringKey:elementName]];
  570.     [self insertStringKey:":line-pos" value:NULL];
  571.     [self insertStringKey:":para-id" value:NULL];
  572.     }
  573.  
  574.     // Standard write.
  575.     return [super writeWaisStruct:structName
  576.     forElement:elementName toFile:file withDecoder:theDecoder];
  577. }
  578.  
  579. - writeWaisFile
  580. {
  581.     NXAtom orig_key;
  582.     char buf[MAXPATHLEN+1];
  583.     
  584.     // Fill in missing fields.
  585.     if(![self valueForStringKey:":date"]
  586.         || strlen([self valueForStringKey:":date"])==0)
  587.         [self insertStringKey:":date" value:"0"];
  588.     
  589.     // We write WAIS specification file rather than content file,
  590.     //     so temporarily append ".wais" to key (note call to super
  591.     //     since our -setKey: strips the ".wais").
  592.     if(!key) return nil;
  593.     orig_key = key;
  594.     strcpy(buf, orig_key);
  595.     strcat(buf, W_D_EXT);
  596.     [super setKey:buf];
  597.     if(![super writeWaisFile]) return nil;
  598.     [self setKey:orig_key];
  599.     return self;
  600. }
  601.  
  602. @end
  603.     
  604.  
  605.  
  606.  
  607.  
  608.  
  609.